package es.udima.geo.geoserver.business;

import java.util.Calendar;
import java.util.List;

import es.udima.geo.geoserver.entities.GpsData;
import es.udima.geo.geoserver.entities.GpsDataFilter;
import es.udima.geo.geoserver.entities.GpsDataRetrieveResult;
import es.udima.geo.geoserver.persistence.GpsDataDAO;

public class GpsDataController {

    public GpsDataController() {
    }

    /**
     * UserId in filter is a mandatory parameter. If no date filter is set it
     * will return the gps data of the last day the user sent data. If date
     * filter is for more than MAX_DAYS_TO_RETRIVE_GPS_DATA it will return an
     * error.
     * 
     * @param filter
     * @return
     */
    public GpsDataRetrieveResult retrieveGpsData(GpsDataFilter filter) {

	GpsDataDAO dao = new GpsDataDAO();

	GpsDataFilter resFilter = new GpsDataFilter();
	resFilter.setUserId(filter.getUserId());

	// si se ha activado el atributo userId pero ninguna de las fechas se
	// recuperan los datos gps del ultimo dia disponible del usuario
	if (!filter.getUserId().isEmpty() && filter.getInitDate() == null && filter.getEndDate() == null) {

	    // establecemos como fecha de inicio y fin la ultima disponible del
	    // usuario
	    Calendar initDate = dao.retrieveLastDayGpsData(resFilter.getUserId());
	    if (initDate != null) {
		resFilter.setInitDate(initDate);
		resFilter.setEndDate(initDate);
	    }
	} else {
	    resFilter.setInitDate(filter.getInitDate());
	    resFilter.setEndDate(filter.getEndDate());
	}

	List<GpsData> gpsData = dao.retrieveGpsData(resFilter);

	if (gpsData != null) {
	    calculateSpeedData(gpsData);
	}

	GpsDataRetrieveResult res = new GpsDataRetrieveResult();
	res.setFilter(resFilter);
	res.setGpsData(gpsData);

	return res;
    }

    /**
     * 
     * @param gspData
     */
    public void storeGpsData(List<GpsData> gpsData) {
	GpsDataDAO dao = new GpsDataDAO();
	dao.storeGpsData(gpsData);
    }

    /*------------------------------------------------------------------------------*/

    /**
     * 
     * @param gpsData
     */
    private void calculateSpeedData(List<GpsData> gpsData) {
	GpsData gdPrev = null;
	for (GpsData gd : gpsData) {
	    if (gdPrev == null) {
		gdPrev = gd;
	    } else {
		calculateSpeedData(gdPrev, gd);
		gdPrev = gd;
	    }
	}
    }

    /**
     * Calculate the speed data for the gd2 when coming from gd1
     * 
     * @param gd1
     * @param gd2
     */
    private void calculateSpeedData(GpsData gd1, GpsData gd2) {

	double distance = calculateDistance(gd1.getLat(), gd1.getLon(), gd1.getLat(), gd2.getLon()) * 1000;
	double time = gd2.getFetchTime().getTimeInMillis() - gd1.getFetchTime().getTimeInMillis();
	double speed = (distance / 1000) / (time / (3600 * 1000));

	gd2.setDistanceFromPrevious(distance);
	gd2.setLapseTimeFromPrevious(time);
	gd2.setSpeedFromPrevious(speed);
    }

    /**
     * Formula used returning distance in km for gps coordinates in decimal
     * notation: Distancia = 6371 * ACOS(COS(Lat1) * COS(Lat2) * COS(Long2 -
     * Long1) + SIN(Lat1) * SIN(Lat2))
     * 
     * having: Lat1=(LATITUD1*3.141592654)/180 Lat2=(LATITUD2*3.141592654)/180
     * Long1=(LONGITUD1*3.141592654)/180 Long2=(LONGITUD2*3.141592654)/180
     * 
     * Extracted from: http://www.xoneisp.com/foro/showthread.php?tid=294
     * 
     * @param lat1
     * @param lon1
     * @param lat2
     * @param lon2
     * @return
     */
    private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
	double la1 = lat1 * Math.PI / 180;
	double lo1 = lon1 * Math.PI / 180;
	double la2 = lat2 * Math.PI / 180;
	double lo2 = lon2 * Math.PI / 180;

	double distance = 6371 * Math.acos(Math.cos(la1) * Math.cos(la2) * Math.cos(lo2 - lo1) + Math.sin(la1) * Math.sin(la2));

	return distance;
    }
}
